﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium;

namespace Fluent
{
    public class Finder
    {
        public List<Finder> ParentFinders { get; private set; }
        public FindBy DefinedBy { get; private set; }
        public string Expression { get; private set; }
        public string IFrameName { get; private set; }
        public int CursorIndex { get; private set; }

        public bool IsIFrameElement
        {
            get
            {
                return !string.IsNullOrEmpty(this.IFrameName);
            }
        }

        private Finder(FindBy definedBy, string expression)
        {
            this.DefinedBy = definedBy;
            this.Expression = expression;
            this.ParentFinders = new List<Finder>();
            this.CursorIndex = -1;
        }

        public static Finder Define(FindBy definedBy, string expression)
        {
            return new Finder(definedBy, expression);
        }

        public static Finder ByID(string id)
        {
            return Define(FindBy.ID, id);
        }

        public static Finder ByCssSelector(string cssSelector)
        {
            return Define(FindBy.CssSelector, cssSelector);
        }

        public static Finder ByName(string name)
        {
            return Define(FindBy.Name, name);
        }

        public static Finder ByClassName(string className)
        {
            return Define(FindBy.ClassName, className);
        }

        public static Finder ByTagName(string tagName)
        {
            return Define(FindBy.TagName, tagName);
        }

        public static Finder ByXPath(string xpath)
        {
            return Define(FindBy.XPath, xpath);
        }

        public static Finder ByLinkText(string linkText)
        {
            return Define(FindBy.LinkText, linkText);
        }

        public Finder InIFrame(string iframeName)
        {
            this.IFrameName = iframeName;

            return this;
        }

        public Finder InParent(params Finder[] parentFinder)
        {
            this.ParentFinders.AddRange(parentFinder);

            return this;
        }

        public Finder Index(int index)
        {
            this.CursorIndex = index;

            return this;
        }

        public IWebElement Find(WebPageBase page)
        {
            IWebElement evalElement = null;
            if (this.IsIFrameElement)
            {
                using (new IFrame(page.Browser, this.IFrameName).Using())
                {
                    evalElement = this.Finding(page, this, this.GetParentCursor(page));
                }
            }
            else
            {
                evalElement = this.Finding(page, this, this.GetParentCursor(page));
            }

            return evalElement;
        }

        public ReadOnlyCollection<IWebElement> Finds(WebPageBase page)
        {
            ReadOnlyCollection<IWebElement> evalElements = null;
            if (this.IsIFrameElement)
            {
                using (new IFrame(page.Browser, this.IFrameName).Using())
                {
                    evalElements = this.FindingList(page, this, this.GetParentCursor(page));
                }
            }
            else
            {
                evalElements = this.FindingList(page, this, this.GetParentCursor(page));
            }

            return evalElements;
        }

        private IWebElement GetParentCursor(WebPageBase page)
        {
            IWebElement parentElementCursor = null;
            for (int i = this.ParentFinders.Count - 1; i >= 0; i--)
            {
                parentElementCursor = this.Finding(page, this.ParentFinders[i], parentElementCursor);
            }

            return parentElementCursor;
        }

        private IWebElement Finding(WebPageBase page, Finder finder, IWebElement parentElement)
        {
            IWebElement findingElement = null;

            var matchElementList = this.FindingList(page, finder, parentElement);

            if (matchElementList.Count > 0)
            {
                findingElement = matchElementList[finder.CursorIndex == -1 ? 0 : finder.CursorIndex];
            }

            return findingElement;
        }

        private ReadOnlyCollection<IWebElement> FindingList(WebPageBase page, Finder finder, IWebElement parentElement)
        {
            ReadOnlyCollection<IWebElement> findingElement = null;

            if (parentElement == null)
            {
                switch (finder.DefinedBy)
                {
                    case FindBy.ID:
                        findingElement = page.Browser.SearchContext.FindElements(By.Id(finder.Expression));
                        break;
                    case FindBy.CssSelector:
                        findingElement = page.Browser.SearchContext.FindElements(By.CssSelector(finder.Expression));
                        break;
                    case FindBy.Name:
                        findingElement = page.Browser.SearchContext.FindElements(By.Name(finder.Expression));
                        break;
                    case FindBy.ClassName:
                        findingElement = page.Browser.SearchContext.FindElements(By.ClassName(finder.Expression));
                        break;
                    case FindBy.TagName:
                        findingElement = page.Browser.SearchContext.FindElements(By.TagName(finder.Expression));
                        break;
                    case FindBy.XPath:
                        findingElement = page.Browser.SearchContext.FindElements(By.XPath(finder.Expression));
                        break;
                    case FindBy.InputValue:
                        findingElement = page.Browser.SearchContext.FindElements(By.CssSelector("input[value='" + finder.Expression + "']"));
                        break;
                    case FindBy.Title:
                        findingElement = page.Browser.SearchContext.FindElements(By.CssSelector("[title='" + finder.Expression + "']"));
                        break;
                    case FindBy.LinkText:
                        findingElement = page.Browser.SearchContext.FindElements(By.PartialLinkText(finder.Expression));
                        break;
                    default:
                        throw new NotSupportedException();
                }
            }
            else
            {
                switch (finder.DefinedBy)
                {
                    case FindBy.ID:
                        findingElement = parentElement.FindElements(By.Id(finder.Expression));
                        break;
                    case FindBy.CssSelector:
                        findingElement = parentElement.FindElements(By.CssSelector(finder.Expression));
                        break;
                    case FindBy.Name:
                        findingElement = parentElement.FindElements(By.Name(finder.Expression));
                        break;
                    case FindBy.ClassName:
                        findingElement = parentElement.FindElements(By.ClassName(finder.Expression));
                        break;
                    case FindBy.TagName:
                        findingElement = parentElement.FindElements(By.TagName(finder.Expression));
                        break;
                    case FindBy.XPath:
                        findingElement = parentElement.FindElements(By.XPath(finder.Expression));
                        break;
                    case FindBy.InputValue:
                        findingElement = parentElement.FindElements(By.CssSelector("input[value='" + finder.Expression + "']"));
                        break;
                    case FindBy.Title:
                        findingElement = parentElement.FindElements(By.CssSelector("[title='" + finder.Expression + "']"));
                        break;
                    case FindBy.LinkText:
                        findingElement = parentElement.FindElements(By.PartialLinkText(finder.Expression));
                        break;
                    default:
                        throw new NotSupportedException();
                }
            }

            return findingElement;
        }

        public override string ToString()
        {
            return string.Format("FindBy: {0}, Expression: {1}", DefinedBy.ToString(), Expression);
        }

        public Finder Copy()
        {
            return new Finder(this.DefinedBy, this.Expression)
            {
                ParentFinders = this.ParentFinders,
                CursorIndex = this.CursorIndex,
                IFrameName = this.IFrameName
            };
        }
    }

    public enum FindBy
    {
        ID,
        CssSelector,
        Name,
        ClassName,
        TagName,
        XPath,
        InputValue,
        Title,
        LinkText
    }
}